
package oracle.apps.financials.collection.adapter;



import greendroid.widget.item.DescriptionItem;
import greendroid.widget.item.DrawableItem;
import greendroid.widget.item.LongTextItem;
import greendroid.widget.item.ProgressItem;
import greendroid.widget.item.SeparatorItem;
import greendroid.widget.item.SubtextItem;
import greendroid.widget.item.SubtitleItem;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;

import oracle.apps.financials.collection.po.Item;
import oracle.apps.financials.collection.po.ItemView;
import oracle.apps.financials.collection.po.TextItem;
import oracle.apps.financials.collection.po.ThumbnailItem;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import android.content.Context;
import android.content.res.Resources;
import android.util.AttributeSet;
import android.util.Xml;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

/**
 * <p>
 * A ListAdapter that acts like an ArrayAdapter. An ItemAdapter manages a
 * ListView that is backed by an array of {@link Item}s. This is more advanced
 * than a simple ArrayAdapter because it handles different types of itemviews
 * internally. Adding, removing items from the internal array is also possible.
 * </p>
 * <p>
 * The ListView can be notified of underlying data changes manually using
 * notifyDataSetChanged or automatically using the
 * {@link #setNotifyOnChange(boolean)} method.
 * </p>
 * <p>
 * Finally, an ItemAdapter can be created via XML code using the
 * {@link #createFromXml(Context, int)} method. This is a very powerful feature
 * when you want to display static data or if you want to pre-populate your
 * ItemAdapter.
 * </p>
 * 
 
 */
public class ItemAdapter extends BaseAdapter {

	private static final int DEFAULT_MAX_VIEW_TYPE_COUNT = 10;

	private static class TypeInfo {
		int count;
		int type;
	}

	private List<Item> mItems;
	private HashMap<Class<? extends Item>, TypeInfo> mTypes;
	private Context mContext;

	private boolean mNotifyOnChange;
	private int mMaxViewTypeCount;

	/**
	 * Constructs an empty ItemAdapter.
	 * 
	 * @param context
	 *            The context associated with this array adapter.
	 */
	public ItemAdapter(Context context) {
		this(context, new ArrayList<Item>());
	}

	/**
	 * Constructs an ItemAdapter using the specified items.
	 * <p>
	 * <em><strong>Note</strong>: Using this constructor implies the internal array will be
	 * immutable. As a result, adding or removing items will result in an
	 * exception.</em>
	 * </p>
	 * 
	 * @param context
	 *            The context associated with this array adapter.
	 * @param items
	 *            The array of {@link Item}s use as underlying data for this
	 *            ItemAdapter
	 */
	public ItemAdapter(Context context, Item[] items) {
		this(context, Arrays.asList(items), DEFAULT_MAX_VIEW_TYPE_COUNT);
	}

	/**
	 * Constructs an ItemAdapter using the specified items.
	 * <p>
	 * 
	 * @param context
	 *            The context associated with this array adapter.
	 * @param items
	 *            The list of {@link Item}s used as data for this ItemAdapter
	 */
	public ItemAdapter(Context context, List<Item> items) {
		this(context, items, DEFAULT_MAX_VIEW_TYPE_COUNT);
	}

	/**
	 * Constructs an ItemAdapter using the specified items.
	 * <p>
	 * <em><strong>Note</strong>: Using this constructor implies the internal array will be
	 * immutable. As a result, adding or removing items will result in an
	 * exception.</em>
	 * </p>
	 * <p>
	 * <em><strong>Note</strong>: A ListAdapter doesn't handle variable view type 
	 * count (even after a notifyDataSetChanged). An ItemAdapter handles several 
	 * types of cell are therefore use a trick to overcome the previous problem.
	 * This trick is to fool the ListView several types exist. If you already
	 * know the number of item types you'll possibly have, simply set it using this method</em>
	 * </p>
	 * 
	 * @param context
	 *            The context associated with this array adapter.
	 * @param items
	 *            The array of {@link Item}s use as underlying data for this
	 *            ItemAdapter
	 * @param maxViewTypeCount
	 *            The maximum number of view type that may be generated by this
	 *            ItemAdapter
	 */
	public ItemAdapter(Context context, Item[] items, int maxViewTypeCount) {
		this(context, Arrays.asList(items), maxViewTypeCount);
	}

	/**
	 * Constructs an ItemAdapter using the specified items.
	 * <p>
	 * <em><strong>Note:</strong> A ListAdapter doesn't handle variable view type 
	 * count (even after a notifyDataSetChanged). An ItemAdapter handles several 
	 * types of cell are therefore use a trick to overcome the previous problem.
	 * This trick is to fool the ListView several types exist. If you already
	 * know the number of item types you'll possibly have, simply set it using this method</em>
	 * </p>
	 * 
	 * @param context
	 *            The context associated with this array adapter.
	 * @param items
	 *            The list of Items used as data for this ItemAdapter
	 * @param maxViewTypeCount
	 *            The maximum number of view type that may be generated by this
	 *            ItemAdapter
	 */
	public ItemAdapter(Context context, List<Item> items, int maxViewTypeCount) {
		mContext = context;
		mItems = items;
		mTypes = new HashMap<Class<? extends Item>, TypeInfo>();
		mMaxViewTypeCount = Integer.MAX_VALUE;

		for (Item item : mItems) {
			addItem(item);
		}

		mMaxViewTypeCount = Math.max(1, Math.max(mTypes.size(), maxViewTypeCount));
	}

	public void resetAdapter(List<Item> list) {
		this.clear();
		Iterator<Item> it = list.iterator();
		while (it.hasNext()) {
			this.add(it.next());
		}
		this.notifyDataSetChanged();

	}
	
	public void setHighlight(int position) {
		for (Item item :getItems()) {
			item.setHighlight(false);
		}
		Item item = (Item) this.getItem(position);
		item.setHighlight(true);
		this.notifyDataSetChanged();
	}

	private void addItem(Item item) {
		final Class<? extends Item> klass = item.getClass();
		TypeInfo info = mTypes.get(klass);

		if (info == null) {
			final int type = mTypes.size();
			if (type >= mMaxViewTypeCount) {
				throw new RuntimeException("This ItemAdapter may handle only " + mMaxViewTypeCount
						+ " different view types.");
			}
			final TypeInfo newInfo = new TypeInfo();
			newInfo.count = 1;
			newInfo.type = type;
			mTypes.put(klass, newInfo);
		} else {
			info.count++;
		}
	}

	private void removeItem(Item item) {
		final Class<? extends Item> klass = item.getClass();
		TypeInfo info = mTypes.get(klass);

		if (info != null) {
			info.count--;
			if (info.count == 0) {
				// TODO cyril: Creating a pool to keep all TypeInfo instances
				// could be a great idea in the future.
				mTypes.remove(klass);
			}
		}
	}

	/**
	 * Returns the context associated with this array adapter. The context is
	 * used to create Views from the resource passed to the constructor.
	 * 
	 * @return The Context associated to this ItemAdapter
	 */
	public Context getContext() {
		return mContext;
	}

	/**
	 * Returns the current number of different views types used in this
	 * ItemAdapter. Having a <em>getCurrentViewTypeCount</em> equal to
	 * <em>getViewTypeCount</em> means you won't be able to add a new type of
	 * view in this adapter (The Adapter class doesn't allow variable view type
	 * count).
	 * 
	 * @return The current number of different view types
	 */
	public int getActualViewTypeCount() {
		return mTypes.size();
	}

	/**
	 * Adds the specified object at the end of the array.
	 * 
	 * @param item
	 *            The object to add at the end of the array.
	 */
	public void add(Item item) {
		mItems.add(item);
		addItem(item);
		if (mNotifyOnChange) {
			notifyDataSetChanged();
		}
	}

	/**
	 * Inserts the specified object at the specified index in the array.
	 * 
	 * @param item
	 *            The object to insert into the array.
	 * @param index
	 *            The index at which the object must be inserted.
	 */
	public void insert(Item item, int index) {
		mItems.add(index, item);
		addItem(item);
		if (mNotifyOnChange) {
			notifyDataSetChanged();
		}
	}

	/**
	 * Removes the specified object from the array.
	 * 
	 * @param item
	 *            The object to remove.
	 */
	public void remove(Item item) {
		if (mItems.remove(item)) {
			removeItem(item);
			if (mNotifyOnChange) {
				notifyDataSetChanged();
			}
		}
	}

	/**
	 * Remove all elements from the list.
	 */
	public void clear() {
		mItems.clear();
		mTypes.clear();
		if (mNotifyOnChange) {
			notifyDataSetChanged();
		}
	}

	/**
	 * Sorts the content of this adapter using the specified comparator.
	 * 
	 * @param comparator
	 *            The comparator used to sort the objects contained in this
	 *            adapter.
	 */
	public void sort(Comparator<? super Item> comparator) {
		Collections.sort(mItems, comparator);
		if (mNotifyOnChange) {
			notifyDataSetChanged();
		}
	}

	/**
	 * Control whether methods that change the list ({@link #add},
	 * {@link #insert}, {@link #remove}, {@link #clear}) automatically call
	 * notifyDataSetChanged(). If set to false, caller must manually call
	 * notifyDataSetChanged() to have the changes reflected in the attached
	 * view. The default is true, and calling notifyDataSetChanged() resets the
	 * flag to true.
	 * 
	 * @param notifyOnChange
	 *            if true, modifications to the list will automatically call
	 *            notifyDataSetChanged()
	 */
	public void setNotifyOnChange(boolean notifyOnChange) {
		mNotifyOnChange = notifyOnChange;
	}

	/**
	 * Creates an ItemAdapter from a given resource ID
	 * 
	 * @param context
	 *            The Context in which the ItemAdapter will be used in
	 * @param xmlId
	 *            The resource ID of an XML file that describes a set of
	 *            {@link Item}
	 * @return a new ItemAdapter constructed with the content of the file
	 *         pointed by <em>xmlId</em>
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	public static ItemAdapter createFromXml(Context context, int xmlId) throws XmlPullParserException, IOException {
		return createFromXml(context, context.getResources().getXml(xmlId));
	}

	/**
	 * Creates an ItemAdapter from a given XML document. Called on a parser
	 * positioned at a tag in an XML document, tries to create an ItemAdapter
	 * from that tag.
	 * 
	 * @param context
	 *            The Context in which the ItemAdapter will be used in
	 * @param parser
	 *            The XmlPullParser
	 * @return a new ItemAdapter constructed with the content of the file
	 *         pointed by <em>xmlId</em>
	 * @throws XmlPullParserException
	 * @throws IOException
	 */
	public static ItemAdapter createFromXml(Context context, XmlPullParser parser) throws XmlPullParserException,
			IOException {
		AttributeSet attrs = Xml.asAttributeSet(parser);

		int type;
		while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
			// Empty loop
		}

		if (type != XmlPullParser.START_TAG) {
			throw new XmlPullParserException("No start tag found");
		}

		if (!parser.getName().equals("item-array")) {
			throw new XmlPullParserException("Unknown start tag. Should be 'item-array'");
		}

		final List<Item> items = new ArrayList<Item>();
		final int innerDepth = parser.getDepth() + 1;
		final Resources r = context.getResources();

		int depth;
		while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
				&& ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
			if (type != XmlPullParser.START_TAG) {
				continue;
			}

			if (depth > innerDepth) {
				continue;
			}

			String name = parser.getName();
			Item item;
			if (name.equals("text-item")) {
				item = new TextItem();
			} else {
				// TODO cyril: Remove that so that we can extend from
				// ItemAdapter and creates our own items via XML?
				throw new XmlPullParserException(parser.getPositionDescription() + ": invalid item tag " + name);
			}

			// TODO cyril: Here we should call a method that children may
			// override to be able to create our own Items

			if (item != null) {
				item.inflate(r, parser, attrs);
				items.add(item);
			}
		}

		return new ItemAdapter(context, items);
	}

	public int getCount() {
		return mItems.size();
	}

	public Object getItem(int position) {
		return mItems.get(position);
	}

	public long getItemId(int position) {
		return position;
	}

	@Override
	public int getItemViewType(int position) {
		return mTypes.get(getItem(position).getClass()).type;
	}

	@Override
	public boolean isEnabled(int position) {
		return ((Item) getItem(position)).enabled;
	}

	@Override
	public int getViewTypeCount() {
		return mMaxViewTypeCount;
	}

	public View getView(int position, View convertView, ViewGroup parent) {

		final Item item = (Item) getItem(position);
		ItemView cell = (ItemView) convertView;

		if (cell == null) {
			cell = item.newView(mContext, null);
			cell.prepareItemView();
		}

		cell.setObject(item);
		

		return (View) cell;
	}

	public List<Item> getItems() {
		return mItems;
	}

	public void setItems(List<Item> mItems) {
		this.mItems = mItems;
	}

}
